package com.masonluo.mlonlinejudge.service.impl;


import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.masonluo.mlonlinejudge.dao.*;
import com.masonluo.mlonlinejudge.entity.ExperimentProblemResult;
import com.masonluo.mlonlinejudge.entity.ExperimentScore;
import com.masonluo.mlonlinejudge.entity.ExperimentScoreResult;
import com.masonluo.mlonlinejudge.mapper.ExperimentResultMapper;
import com.masonluo.mlonlinejudge.model.bo.StudentBo;
import com.masonluo.mlonlinejudge.model.dto.ExperimentScoreResultDto;
import com.masonluo.mlonlinejudge.model.vo.ExperimentScoreResultVo;
import com.masonluo.mlonlinejudge.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * <p>
 * 实验结果表 服务实现类
 * </p>
 *
 * @author LOTP
 * @since 2021-11-05
 */
@Service
public class ExperimentResultServiceImpl extends ServiceImpl<ExperimentResultRepository, ExperimentScoreResult> implements ExperimentResultService {

    @Autowired
    private ExperimentResultRepository experimentResultRepository;
    @Autowired
    private ClassService classService;
    @Autowired
    private ClassRepository classRepository;
    @Autowired
    private ExperimentService experimentService;
    @Autowired
    private ExperimentProblemRepository experimentProblemRepository;
    @Autowired
    private SolutionService solutionService;
    @Autowired
    private UserClassRepository userClassRepository;
    @Autowired
    private ExperimentResultMapper experimentResultMapper;
    @Autowired
    private ExperimentSchoolRepository experimentSchoolRepository;

    @Override
    public List<ExperimentScoreResultDto> findScoreByUserId(Integer userId) {
        List<ExperimentScoreResult> experimentScoreResult = experimentResultRepository.findScoreByUserId(userId);
        return experimentResultMapper.doToDto(experimentScoreResult);
    }

    @Override
    public List<ExperimentScoreResultVo> findScoreByTeacherIdAndClassId(Integer teacherId, Integer classId, Integer pageNum, Integer pageSize){
        /**
         * 该接口是需要分页的
         * 这里的一个巧妙之处就是，直接调用classService.getStudentByClassId()分页查找出前端需要的userIds
         * 将这个userIds直接传给findScoreByUserIds(),这样子就免去了该接口的分页
         */
        List<StudentBo> studentBos = classService.getStudentByClassId(classId, teacherId, pageNum, pageSize);
        List<Integer> studentIds = new ArrayList<>();
        studentBos.forEach(studentBo -> {
            studentIds.add(studentBo.getUserId());
        });
        return this.findScoreByUserIds(studentIds);
    }

    @Override
    public List<ExperimentScoreResultVo> refreshScoreByTeacherIdAndClassId(Integer teacherId, Integer classId, Integer pageNum, Integer pageSize){
        List<StudentBo> studentBos = classService.getStudentByClassId(classId, teacherId, null, null);
        List<Integer> studentIds = new ArrayList<>();
        studentBos.forEach(studentBo -> {
            studentIds.add(studentBo.getUserId());
        });
        return this.refreshExperimentScoreResult(teacherId,studentIds,classId,pageNum,pageSize);
    }

    @Override
    public List<ExperimentScoreResultVo> bulidScoreByTeacherIdAndClassId(Integer teacherId, Integer classId){
        List<StudentBo> studentBos = classService.getStudentByClassId(classId, teacherId, null, null);
        List<Integer> studentIds = new ArrayList<>();
        studentBos.forEach(studentBo -> {
            studentIds.add(studentBo.getUserId());
        });
        return this.buildScore(teacherId,classId,studentIds);
    }


    public List<ExperimentScoreResultVo> buildScore(Integer teacherId,Integer classId, List<Integer> userIds){

        /**
         * 重构成绩表（先删除已存在的学生成绩）
         **/
        experimentResultRepository.deleteByClassId(classId);

        /**
         * 由于t_experiment_result表中数据是空的，
         * 所以我们需要在其它表中，联表查询到相关的数据，再插入到t_experiment_result中
         * 即入库操作
         * 因此在每一次对t_experiment_result表中的数据进行查询时，我们都要进行入库或更新
         * */
        int schoolId = classRepository.selectByPrimaryKey(classId).getSchoolId();

        List<ExperimentScoreResultDto> experimentScoreResultDtoList = experimentResultRepository.findExperimentByClassIdAndSchoolId(classId,schoolId);
        List<ExperimentScore> experimentScores = experimentResultRepository.findScoreByClassIdAndSchoolId(classId,schoolId);
        //以上两个列表的大小和排列顺序是一样的，都是在一个视图里查找的
        int enableExperiment = experimentScores.size();
        List<ExperimentScoreResultDto> finalExperimentScoreResults = new ArrayList<>();
        /**
         * 直接从t_experiment_result表中的数据样式如下:
         * [{
         *       "userId": 671,
         *       "userName": "阿立",
         *       "experimentScore": [
         *         {
         *           "id": 104,
         *           "name": "算法初试",
         *           "passNum": 0,
         *           "total": 7
         *         }
         *       ]
         *     },
         {
         *       "userId": 671,
         *       "userName": "阿立",
         *       "experimentScore": [
         *         {
         *           "id": 108,
         *           "name": "实验三",
         *           "passNum": 0,
         *           "total": 7
         *         }
         *       ]
         *     }]
         * 前端所需要的格式如下：
         * {
         *       "userId": 671,
         *       "userName": "阿立",
         *       "experimentScore": [
         *         {
         *           "id": 104,
         *           "name": "算法初试",
         *           "passNum": 0,
         *           "total": 7
         *         },
         *         {
         *           "id": 108,
         *           "name": "实验三",
         *           "passNum": 0,
         *           "total": 7
         *         }
         *       ]
         *  }
         *
         */
        /**
         * 将experimentScore赋值给对应的experimentScoreResultDto
         * 在这个过程结束后，将得到的 @para experimentResults,直接插入t_experiment_result表中。
         * 还可以直接将该学生的每一个成绩表experimentScore合并直接返回给前端了。
         * 就不需要入库之后再从t_experiment_result表中查找数据之后在进行合并各个学生的各个成绩表，节省时间
         */
        for (int i = 0; i < experimentScoreResultDtoList.size(); i++) {
            experimentScoreResultDtoList.get(i).setExperimentScore(Collections.singletonList(experimentScores.get(i)));

            /*List<ExperimentScore> experimentScoreTemp = experimentScores;
            experimentScoreTemp.get(i % (enableExperiment-1)).setPassNum(experimentScoreResultDtoList.get(i).getPassNum());
            experimentScoreResultDtoList.get(i).setExperimentScore(Collections.singletonList(experimentScoreTemp.get(i % (enableExperiment-1))));
            //如果可以整除实验的数量，说明一个人的实验总成绩已经初始化好了。
            if (i % (enableExperiment-1) == 0){
                //将该学生的每个实验成绩表合并到一个列表中
                experimentScoreResultDtoList.get(i).setExperimentScore(experimentScoreTemp);
                finalExperimentScoreResults.add(experimentScoreResultDtoList.get(i));
                //重新传值，用于后续插入数据库
                experimentScoreResultDtoList.get(i).setExperimentScore(Collections.singletonList(experimentScores.get(i % (enableExperiment-1))));
            }*/
        }
        this.insertExperimentScoreResult(experimentResultMapper.dtoToDo(experimentScoreResultDtoList),schoolId,classId);

        //手动分页，这里需要手动分页的原因是我一次性就把该班级的所有学生的实验成绩入库了，而不是仅仅只入库分页查询所需要的那几个学生
        /*if ((pageNum - 1) * pageSize > finalExperimentScoreResults.size()) {
            throw new ResourceNotFoundException(String.format("The pageNum [%d] does not exist.", pageNum));
        }
        return experimentResultMapper.dtoToVo(finalExperimentScoreResults).subList((pageNum - 1) * pageSize, Math.min(pageNum * pageSize, finalExperimentScoreResults.size()));
        */
        return findScoreByUserIds(userIds);
        //return null;

    }



    public List<ExperimentScoreResultVo> findScoreByUserIds(List<Integer> userIds) {

        int userNum = userIds.size();
        if (userNum > 0) {
            List<ExperimentScoreResultDto> experimentScoreResults = experimentResultMapper.doToDto(experimentResultRepository.findScoreByUserIds(userIds));
            int experimentNum = experimentService.countByUserId(userIds.get(0),"ENABLE" );
            List<ExperimentScoreResultDto> finalExperimentScoreResults = new ArrayList<>();

            for (int i = 0; i < experimentScoreResults.size(); i = i + experimentNum){
                ExperimentScoreResultDto experimentScoreResultDto = experimentScoreResults.get(i);
                List<ExperimentScore> experimentScores = experimentScoreResults.get(i).getExperimentScore();
                for (int j = 1; j < experimentNum; j++){
                    experimentScores.add(experimentScoreResults.get(j+i).getExperimentScore().get(0));
                }
                experimentScoreResultDto.setExperimentScore(experimentScores);
                finalExperimentScoreResults.add(experimentScoreResultDto);
            }



            /*userIds.forEach(userId -> {
                //List<ExperimentScoreResultDto> experimentScoreResultDtos = new ArrayList<>();
                ExperimentScoreResultDto experimentScoreResultDto = experimentScoreResults.get(0);
                List<ExperimentScore> experimentScore = experimentScoreResultDto.getExperimentScore();
                for (int i = 1; i < experimentScoreResults.size(); i++) {
                    List<ExperimentScore> experimentScores = experimentScoreResults.get(i).getExperimentScore();
                    experimentScore.addAll(experimentScores);
                }
                experimentScoreResultDto.setExperimentScore(experimentScore);
                finalExperimentScoreResults.add(experimentScoreResultDto);
            });*/

        return experimentResultMapper.dtoToVo(finalExperimentScoreResults);
        } else {
            return null;
        }
    }


    //根据userIds批量更新成绩
    public List<ExperimentScoreResultVo> refreshExperimentScoreResult(Integer teacherId,List<Integer> userIds,Integer classId, Integer pageNum, Integer pageSize){

        //可能会存在删除了了一些实验，又关联了一些新实验的情况
        //查找出被删除的实验并删除对应的成绩
        //更改需求：一旦实验有成绩，则不能被删除
        /*List<Integer> deletedExperimentIds = experimentResultRepository.findDeletedExperimentIds(userIds.get(0));
        if (deletedExperimentIds.size() != 0){
            experimentResultRepository.deleteByExperimentIds(deletedExperimentIds);
        }*/
        //删除了某些同学，那么该学生的实验成绩也要被删除
        /*
            List<Integer> deletedUserIds = userClassRepository.findDeletedUserIds();
        */

        int schoolId = classRepository.selectByPrimaryKey(classId).getSchoolId();

        List<ExperimentScoreResultDto> updateExperimentScoreResults = new ArrayList<>();
        //比较哪些成绩是否发生改变，筛选出需要改变的成绩表进行更新

        /**
         * 以下三个列表的顺序和大小都是一致的，数据都是一一对应的，从数据查询出时已排好序
         */
        //视图中的最新成绩
        //List<ExperimentScoreResultDto> experimentScoreResultDtoList = experimentResultRepository.findExperimentByClassIdAndSchoolId(classId,schoolId);
        List<ExperimentScore> experimentScores = experimentResultRepository.findScoreByClassIdAndSchoolId(classId,schoolId);
        //表中的旧成绩
        List<ExperimentScoreResultDto> experimentScoreResults = experimentResultMapper.doToDto(experimentResultRepository.findScoreByUserIds(userIds));
        if (Objects.isNull(experimentScoreResults) || experimentScoreResults.size() == 0){
            throw new IllegalArgumentException("请先构建成绩表！");
        }
        for (int i = 0; i < experimentScoreResults.size(); i++) {
            if (!Objects.equals(experimentScoreResults.get(i).getExperimentScore().get(0).getPassNum(), experimentScores.get(i).getPassNum())){
                //experimentScoreResults.get(i).getExperimentScore().get(0).setPassNum(experimentScores.get(i).getPassNum());
                experimentScoreResults.get(i).setExperimentScore(Collections.singletonList(experimentScores.get(i)));
            }
            updateExperimentScoreResults.add(experimentScoreResults.get(i));
        }

        /*userIds.forEach(userId->{
            List<ExperimentScoreResult> experimentScoreResultList = experimentResultRepository.findScoreByUserId(userId);
            if (Objects.isNull(experimentScoreResultList) || experimentScoreResultList.size() == 0){
                throw new IllegalArgumentException("请先构建成绩表！");
            }
            List<ExperimentScoreResultDto> experimentScoreResults = experimentResultMapper.doToDto(experimentScoreResultList);
            for (int j = 0; j<experimentScoreResults.size(); j++){
                int oldPassNum = experimentScoreResults.get(j).getExperimentScore().get(0).getPassNum();
                int newPassNum = solutionService.countPassProblemByUserIdAndExperimentId(userId, experimentScoreResults.get(j).getExperimentId());
                if (oldPassNum < newPassNum ){
                    experimentScoreResults.get(j).getExperimentScore().get(0).setPassNum(newPassNum);

                }else {
                    //updateExperimentScoreResults.add(experimentScoreResults.get(j));
                    experimentScoreResults.remove(j);
                    j--;
                }
                //experimentScoreResultDtos.get(j).setExperimentScore(Collections.singletonList(experimentScores.get(j)));
            }
            updateExperimentScoreResults.addAll(experimentScoreResults);
        });*/
        //如果学生成绩都没变化，就不需要进行更新
        if (updateExperimentScoreResults.size() > 0){
            this.updateExperimentScoreResult(experimentResultMapper.dtoToDo(updateExperimentScoreResults));
        }
        return this.findScoreByTeacherIdAndClassId(teacherId, classId, pageNum, pageSize);
        //return experimentScoreResults;

    }

    @Override
    public boolean insertExperimentScoreResult(List<ExperimentScoreResult> experimentScoreResults,Integer schoolId, Integer classId) {
        return experimentResultRepository.insertExperimentScoreResult(experimentScoreResults,schoolId,classId);
    }

    @Override
    public boolean updateExperimentScoreResult(List<ExperimentScoreResult> experimentScoreResults) {
        return experimentResultRepository.updateExperimentScoreResult(experimentScoreResults);
    }

    @Override
    public List<ExperimentProblemResult> findExperimentProblemResult(Integer userId, Integer experimentId) {

        //先更新再查，本来跟随整个表一起更新，不过这样子的话每次查询实验成绩就都得更新每道题目的status，太浪费时间
        //前端的设计是点击某按钮查看该实验每道题的status，所以可以不跟随整个表一起更新，而是点击了该按钮才更新
        Map map = new HashMap();
        map.put("user_id",userId);
        map.put("experiment_id",experimentId);
        List<ExperimentScoreResult> experimentResults = experimentResultRepository.selectByMap(map);
        if (experimentResults.size() == 0){
            //throw new ResourceNotFoundException(String.format("该用户无此实验成绩"));
            return null;
        }

        //该实验的题目可能会有变化
        List<ExperimentProblemResult> newExperimentProblemResults = experimentProblemRepository.findProblemNameAndIdByExperimentId(experimentId);

        newExperimentProblemResults.forEach(experimentProblemResult -> {
            boolean successStatus = solutionService.judgeSuccessByProblemIDAndUserId(experimentId,experimentProblemResult.getProblemId(), userId);
            boolean doneStatus = solutionService.judgeDoneByProblemIDAndUserId(experimentId, experimentProblemResult.getProblemId(), userId);
            if (doneStatus){
                experimentProblemResult.setStatus("NeverDo");
            }else {
                if (successStatus){
                    experimentProblemResult.setStatus("PASS");
                }else {
                    experimentProblemResult.setStatus("FAIL");
                }
            }
        });


        experimentResults.get(0).setExperimentProblemResult(ExperimentProblemResult.toJsonStr(newExperimentProblemResults));
        experimentResultRepository.updateById(experimentResults.get(0));

        return newExperimentProblemResults;
    }




}
